using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Linq.Expressions;

using GLib;
using Gtk;


namespace DaphneTranslator
{
	public delegate bool TFilterDelegate(object row);
	public delegate void UpdateRowDelegate(object obj, string sPropertyName, string sValue);
	public delegate void TContextMenuDelegate(object obj, string sPropertyName);
	
	

	public class DataGridColumn
	{
		private string columnName = "";
		private TreeViewColumn treeViewColumn = null;
		public CellRendererText Renderer = null; // should be internal
		public string ColumnColor = string.Empty;
				
	
		public string ColumnName {
			get {
				return columnName;
			}
			set {
				columnName = value;
			}
		}
			
		public string PropertyName
		{ get; set; }
				
		public TreeViewColumn TreeViewColumn {
			get {
				return treeViewColumn;
			}
			set {
				treeViewColumn = value;
			}
		}
	}		


	public class DataGrid : VBox {
		private ListStore store;
		private TreeView treeView;
		private string _clrWhite = "#FFFFFF";
	
		 public ArrayList gridColumns; // TODO: make Columns a collection
		
		public event UpdateRowDelegate UpdateRowEvent;
		public event TContextMenuDelegate ContextMenuEvent;
		

	 	public DataGrid () : base(false, 4)
		{
			ScrolledWindow sw = new ScrolledWindow ();
			this.PackStart (sw, true, true, 0);
	
			treeView = new TreeView (store);
			treeView.HeadersVisible = true;
			treeView.EnableGridLines = TreeViewGridLines.Both;
			treeView.ButtonReleaseEvent += HandleButtonReleaseEvent;
	
			gridColumns = new ArrayList (0);
		
			sw.Add (treeView);
		
			store = new ListStore (GLib.GType.String);
		
			treeView.EnableSearch = true;
			treeView.HeadersClickable = true;
			dataMember = "";
			dataSource = null;
		}


	ArrayList bindrows = null;
	ArrayList filterbindrows = null;
	object resolvedDataSource = null;


	private object dataSource;

	private string dataMember;

	public int SelectedRow {
		get {
			TreeIter iter;
			TreeModel model;
			TreeSelection selection = treeView.Selection;
			if (selection.GetSelected (out model, out iter)) {
				TreePath[] path = selection.GetSelectedRows (out model);
			return path[0].Indices[0]; // return selected row
			}
		else
			return -1; // not selected
	 	}
	 }

	public TreeIter SelectedIter {
		get {
			TreeIter iter;
			TreeModel model;
			TreeSelection selection = treeView.Selection;
			if (selection.GetSelected (out model, out iter))
				return iter; // return seelcted iter
			else
			return TreeIter.Zero; // not selected
		}
	}

	public TreeView View
	{
		get
		{
			return treeView;
		}
	}

				
	public object DataSource
	{
		get
		{
			return dataSource;
		}
		set
		{
			dataSource = value;
		}
	}

	public string DataMember
	{
		get
		{
			return dataMember;
		}
		set
		{
			dataMember = value;
		}
	}

	public ListStore Store
	{
		get
		{
			return store;
		}
	}

	public ArrayList Columns
	{
		get
		{
			return gridColumns;
		}
	}


	public int AddNew ()
		{
			// TODO: need to check if resolved data source is not null

			IBindingList b = (IBindingList)resolvedDataSource;

			if (b.AllowNew) {
				object obj = b.AddNew ();
				if (obj == null) {
					//Console.Error.WriteLine("obj is null");
				} else {
					//Console.Error.WriteLine("Type: " + obj.GetType().ToString());
					bindrows.Add (obj);
					filterbindrows.Add (obj);
	
				TreeIter iter = NewRow();
				for(int i = 0; i < gridColumns.Count * 2; i++)
					SetColumnValue (iter, i, String.Empty);
		
				return bindrows.Count - 1;
			}
		}
		
		return -1;
 	}

	public int DeleteRow (int row)
		{
			if (row < 0)
				return -1; // should throw an exception - out of range

			TreeIter iter = TreeIter.Zero;
			if (store.IterNthChild (out iter, row) == false)
				return -1;

			IBindingList b = (IBindingList)resolvedDataSource;
			if (b.AllowRemove) {
				IList list = (IList)resolvedDataSource;
				bindrows.RemoveAt (row);
				filterbindrows.RemoveAt (row);	
			store.Remove (ref iter);
			list.RemoveAt (row);

			return row;
		}

		return -1;
	 }

		 // sets the column count.  beware, it clears
	 // use this if you are going to load each column and row yourself
	 // instead of using DataBind() or DataLoad()
		public void SetColumnCount (int columnCount)
		{
			Clear ();
			dataMember = "";
			dataSource = null;

			GLib.GType[] theTypes = new GLib.GType[columnCount];
			gridColumns = new ArrayList ();
			for (int col = 0; col < columnCount; col++) {
				theTypes [col] = GLib.GType.String;
				gridColumns.Add (new DataGridColumn ());
			}
			store.ColumnTypes = theTypes;
		}
		
		public void FilterRows (TFilterDelegate filterDelegate)
		{
			if (store == null)
				return;
			if (bindrows == null)
				return;
			if (filterbindrows == null)
				return;
			
			// clear the store
			store.Clear ();
			filterbindrows.Clear ();
			
			// prepare map of column indexes
			Dictionary<string, int> columnIndexes = new Dictionary<string, int> ();
			for (int i = 0; i < gridColumns.Count; i++) {
				DataGridColumn gridCol = gridColumns [i] as DataGridColumn;
				columnIndexes [gridCol.PropertyName] = i;
			}
			
			
			// iterate through each row
			foreach (object item in bindrows) {
				if (filterDelegate (item) == true) {
					PropertyInfo[] properties = item.GetType ().GetProperties (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
					
					TreeIter iter = new TreeIter ();
					iter = NewRow ();
					foreach (PropertyInfo property in properties) {
						object oPropValue = property.GetValue (item, new object[] {});
						string sPropValue = "";
						sPropValue = oPropValue.ToString ();
					
	
						SetColumnValue (iter, columnIndexes [property.Name], sPropValue);
					}
				
					SetRowColor (iter, _clrWhite, false);
					filterbindrows.Add (item);
				} // if								
			} // foreach	        				
		}
			

		// load data from a data table or data set
		public long DataBind ()
		{
			long rowsRetrieved = 0;

			Clear (true);		

			IEnumerable ie = (IEnumerable)this.DataSource;
			TreeIter iter = new TreeIter ();

			bindrows = new ArrayList ();
			filterbindrows = new ArrayList ();
			
			GType[] arrayTypes = new GLib.GType[gridColumns.Count * 2];
			for (int i = 0; i < gridColumns.Count * 2; i++)
				arrayTypes [i] = GType.String;
			store = new ListStore (arrayTypes);		
			
			Dictionary<string, int> columnIndexes = new Dictionary<string, int> ();
			for (int i = 0; i < gridColumns.Count; i++) {
				DataGridColumn gridCol = gridColumns [i] as DataGridColumn;
				columnIndexes [gridCol.PropertyName] = i;
			}

			foreach (System.Object obj in ie) {
				PropertyInfo[] properties = obj.GetType ().GetProperties (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

				bindrows.Add (obj);
				filterbindrows.Add (obj);

				rowsRetrieved ++;
				iter = NewRow ();
				foreach (PropertyInfo property in properties) {
					object oPropValue = property.GetValue (obj, new object[] {});
					string sPropValue = "";
					sPropValue = oPropValue.ToString ();
					
	
					SetColumnValue (iter, columnIndexes [property.Name], sPropValue);
				}
				
				SetRowColor (iter, _clrWhite, false);				
			}
	
			treeView.Model = store;
			return rowsRetrieved;
		}


	public void Clear (bool oPreserveColumns = false)
	{
		if (store != null)
		{
			store.Clear ();
			store = null;
			store = new ListStore (GLib.GType.String);
		}
		else
			store = new ListStore (GLib.GType.String);

		if (gridColumns != null && oPreserveColumns == false)
		{
			for (int c = 0; c < gridColumns.Count; c++)
			{
				DataGridColumn gridCol = (DataGridColumn) gridColumns[c];
				if (gridCol.TreeViewColumn != null)
				{
					treeView.RemoveColumn (gridCol.TreeViewColumn);
					gridCol.TreeViewColumn = null;
				}
			}
			gridColumns.Clear ();
			gridColumns = null;
		}
	}

	public TreeIter NewRow ()
	{
		return store.Append();
	}

	public void AddRow (object[] columnValues)
	{
		TreeIter iter = NewRow ();
		for(int col = 0; col < columnValues.Length; col++) {
			string cellValue = columnValues[col].ToString ();
			SetColumnValue (iter, col, cellValue);
		}
	}

		public void SetColumnValue (TreeIter iter, int column, string value)
		{
			GLib.Value cell = new GLib.Value (value);
			store.SetValue (iter, column, cell);
		}
		
		public void SetRowColor (TreeIter iter, string sColor, bool oOverwriteColumns)
		{
			for(int i = 0; i < gridColumns.Count; i++)
			{
				string sCellColor = sColor;
				DataGridColumn column = gridColumns[i] as DataGridColumn;
				if(oOverwriteColumns == false)
				{
					if(string.IsNullOrWhiteSpace(column.ColumnColor) == false)
						sCellColor = column.ColumnColor;
				}	
			
				SetColumnValue (iter, gridColumns.Count + i, sCellColor);
			} // for
		}


	private void AutoCreateTreeViewColumns ()
	{
		for(int col = 0; col < gridColumns.Count; col++) {
			// escape underscore _ because it is used
			// as the underline in menus and labels
			StringBuilder name = new StringBuilder ();
			foreach (char ch in ((DataGridColumn) gridColumns[col]).ColumnName) {
				if (ch == '_')
					name.Append ("__");
				else
					name.Append (ch);
			}
			TreeViewColumn tvc = CreateColumn (col, name.ToString (), name.ToString());
			AppendColumn (tvc);
		}
	}

	public int AppendColumn(TreeViewColumn tvc)
	{
		return treeView.AppendColumn (tvc);
	}
		
	 	void HandleButtonReleaseEvent (object o, ButtonReleaseEventArgs args)
		{
			if (args.Event.Button == 3) {
				if (store == null)
					return;
				if (store.IterNChildren () == 0)
					return;
				
				// detect cell
				TreePath path = new TreePath ();
				TreeViewDropPosition drop;
				int tx, ty;						
				treeView.ConvertBinWindowToWidgetCoords ((int)args.Event.X, (int)args.Event.Y, out tx, out ty);			
				if (treeView.GetDestRowAtPos (tx, ty, out path, out drop) == false) 
					return;
				
				TreeIter iter;
				store.GetIter (out iter, path);
				int i = path.Indices [0];
	
				TreePath cpath;			
				
				// detect object
				TreeViewColumn tvcolumn;
				treeView.GetCursor (out cpath, out tvcolumn);
				int c = 0;
				for (c = 0; c < gridColumns.Count; c++) {
					TreeViewColumn tvc = ((DataGridColumn)gridColumns [c]).TreeViewColumn;
					if (tvcolumn == tvc) {
						break;
					}
				}	

				if (c >= gridColumns.Count)
					return;
				
				if (ContextMenuEvent != null) {
					ContextMenuEvent (filterbindrows [i], ((DataGridColumn)gridColumns [c]).PropertyName);
				}				
			}	 		
	 	}
		

		private void TextCellEdited (object o, EditedArgs args)
		{
			TreePath path = new TreePath (args.Path);
			TreeIter iter;
			store.GetIter (out iter, path);
			int i = path.Indices [0];

			TreePath cpath;
			string cellValue = args.NewText;

			TreeViewColumn tvcolumn;
			treeView.GetCursor (out cpath, out tvcolumn);
			int c = 0;
			for (c = 0; c < gridColumns.Count; c++) {
				TreeViewColumn tvc = ((DataGridColumn)gridColumns [c]).TreeViewColumn;
				if (tvcolumn == tvc) {
					break;
				}
			}	

			if (c >= gridColumns.Count)
				return;
	
			PropertyInfo[] properties = bindrows [i].GetType ()
				.GetProperties (BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			PropertyInfo pd = null;
			foreach (PropertyInfo pi in properties) {
				if (pi.Name == ((DataGridColumn)gridColumns [c]).PropertyName) {
					pd = pi;
					break;
				}				
			}
			
			if (pd == null)
				return;
		
			pd.SetValue (filterbindrows [i], cellValue, new object[] {});
	
			//((IEditableObject) bindrows[i]).BeginEdit();
			SetColumnValue (iter, c, cellValue);
			//((IEditableObject) bindrows[i]).EndEdit();
			if (UpdateRowEvent != null) {
				UpdateRowEvent (filterbindrows [i], ((DataGridColumn)gridColumns [c]).PropertyName, cellValue);
			}			
		}
	
	

		public TreeViewColumn CreateColumn (int columnNum, string columnName, string propertyName, string columnColor = "", bool oEditable = false)
		{
			TreeViewColumn treeViewCol = new TreeViewColumn ();
			CellRendererText renderer = new CellRendererText ();
			treeViewCol.Clickable = true;
			treeView.RulesHint = true;

			treeView.Selection.Mode = SelectionMode.Single;
			//treeView.Selection.Mode = SelectionMode.Multiple;

			// Editable, Activatable, Inert
			if (oEditable == false) {
				renderer.Mode = CellRendererMode.Inert;
			} else {
				renderer.Mode = CellRendererMode.Editable;
			}
			//renderer.Family = "courier new";
			renderer.Foreground = "black";
			renderer.Editable = oEditable;
			renderer.Edited += new EditedHandler (TextCellEdited);
			treeViewCol.Title = columnName;
			treeViewCol.PackStart (renderer, true);
			treeViewCol.AddAttribute (renderer, "text", columnNum);
			treeViewCol.AddAttribute (renderer, "background", gridColumns.Count + columnNum);

			DataGridColumn gridCol = (DataGridColumn)gridColumns [columnNum];
			gridCol.Renderer = renderer;
			gridCol.TreeViewColumn = treeViewCol;
			gridCol.PropertyName = propertyName;
			gridCol.ColumnColor = columnColor;

			return treeViewCol;
		}
 }
}
